#include "stdafx.h"
#include "CScrLanguage.h"
#include "CScripts.h"
#include "CTokens.h"
#include "CParser.h"
#include "CDecompiler.h"

#if 0

FILE			*	CDecompiler::s_hInFile;
FILE			*	CDecompiler::s_hOutFile;
bool				CDecompiler::s_bHexOpen;
bool				CDecompiler::s_bOpcodeNotMask;
bool				CDecompiler::s_bIsInIF;
DWORD				CDecompiler::s_nNumBytesRead;
int					CDecompiler::s_nNumModels;
int					CDecompiler::s_nNumMissions;
int					CDecompiler::s_nNumStreamedScripts;
int					CDecompiler::s_nMainSize;
int					CDecompiler::s_nNumOutputLines;
int					CDecompiler::s_nInputBufferSize;
int					CDecompiler::s_nVarSpaceSize;
char				CDecompiler::s_nFileTargetGame;
DWORD			*	CDecompiler::s_dwModelIDs;
DWORD			*	CDecompiler::s_dwMissionOffsets;
BYTE			*	CDecompiler::s_pInputBuffer;
BYTE			*	CDecompiler::s_pBuffer = nullptr;
COperation		*	CDecompiler::s_pOperation;
COpcode			*	CDecompiler::s_pOpcode;
CVariable		**	CDecompiler::s_Variables;
CConstant		**	CDecompiler::s_Models;

CStreamedScriptInfo * CDecompiler::s_StreamedScriptInfo;

int CDecompiler::Decompile(FILE *hInFile, FILE *hOutFile)
{
	Init(hInFile, hOutFile);

	CLabels::Reset();
	CVariables::Reset();
	CControlStructs::Reset();

	try
	{
		LoadBuffer();
		DecodeHeader();

		s_Variables = new CVariable*[s_nVarSpaceSize / 4];

		for(int i=0; i<s_nVarSpaceSize / 4; i++)
		{
			s_Variables[i] = nullptr;
		}

		while(LoadBuffer())
		{
			do Decode();
			while(!IsEndOfBuffer());
			
		}
		return s_nNumBytesRead;
	}
	catch(const char *e)
	{
		printf("\n");
		Error(e);
	}
	return -1;
}

void CDecompiler::DecodeHeader()
{
	if(FILE * hDefFile = OpenFile("header.def", "rt"))
	{
		DWORD	dwNextOffset;
		int		nSeg,
				nVal,
				nSegID;

		while(char * szLine = GetConfigLine(hDefFile))
		{
			szLine = strtok(szLine, "\t ");

			if(!strncmp("[SEG", szLine, 4))
			{
				szLine += 4;
				nSeg = StringToInt(szLine, szLine);
				if(!(dwNextOffset = DecodeHeaderJump()))
					return;
			}
			else if(!strcmp("VAR_SPACE", szLine))
			{
				SkipTo(dwNextOffset);
			}
			else if(!strcmp("MODEL_NAMES", szLine))
			{
				szLine = strtok(NULL, "\t ");
				nVal = StringToInt(szLine);
				s_dwModelIDs = new DWORD[s_nNumModels];
				s_Models = new CConstant*[s_nNumModels];

				for(int i = 0; i < s_nNumModels; i++)
				{
					ReadString(g_szTempString, nVal);
					
					CConstant * pConstant;
					if(*g_szTempString && (pConstant = CConstants::GetModel(g_szTempString)))
					{
						s_dwModelIDs[i] = pConstant->GetValue<int>();
						s_Models[i] = pConstant;
					}
					else
					{
						s_dwModelIDs[i] = NULL;
						s_Models[i] = nullptr;
					}
				}
			}
			else if(!strcmp("MISSION_OFFSETS", szLine))
			{
				s_dwMissionOffsets = new DWORD[s_nNumMissions];

				for(int i = 0; i < s_nNumMissions; i++)
				{
					s_dwMissionOffsets[i] = Read<DWORD>();
				}
			}
			else if(!strcmp("STREAMED_SCRIPT_INFO", szLine))
			{
				s_StreamedScriptInfo = new CStreamedScriptInfo[s_nNumStreamedScripts];

				for(int i = 0; i < s_nNumStreamedScripts; i++)
				{
					ReadString(s_StreamedScriptInfo[i].m_szName, 20);
					s_StreamedScriptInfo[i].m_nIndex = i;
					s_StreamedScriptInfo[i].m_dwMainOffset = Read<DWORD>();
					s_StreamedScriptInfo[i].m_nSize = Read<DWORD>();
				}
			}
			else
			{
				if(!strcmp("DWORD", szLine)) nVal = Read<DWORD>();
				else if(!strcmp("WORD", szLine)) nVal = Read<WORD>();
				else if(!strcmp("BYTE", szLine)) nVal = Read<BYTE>();
				else THROW("Unknown identifier '%s' in header.def", szLine);

				szLine = strtok(NULL, "\t\n ");

				if(szLine && *szLine)
				{
					if(!strcmp("TargetGame", szLine)) s_nFileTargetGame = nVal;
					else if(!strcmp("SegID", szLine)) nSegID = nVal;
					else if(!strcmp("NumModels", szLine)) s_nNumModels = nVal;
					else if(!strcmp("MainSize", szLine)) s_nMainSize = nVal;
					else if(!strcmp("LargestMissionSize", szLine)) ;
					else if(!strcmp("NumMissions", szLine)) s_nNumMissions = nVal;
					else if(!strcmp("TopLVARUsage", szLine)) ;
					else if(!strcmp("TopStreamedScriptSize", szLine)) ;
					else if(!strcmp("NumStreamedScripts", szLine)) s_nNumStreamedScripts = nVal;
					else if(!strcmp("VarSpaceSize", szLine)) s_nVarSpaceSize = nVal;
					else THROW("Unknown variable '%s' in header.def", szLine);
				}
			}
		}

		CloseFile(hDefFile);
	}
}

DWORD CDecompiler::DecodeHeaderJump()
{
	COpcode *pOpcode;
	do pOpcode = GetOpcode();
	while(pOpcode && pOpcode->GetParseFunc() != SCRIPT_GOTO && pOpcode->GetParseFunc() != SCRIPT_ELSE_GOTO);
	
	if(!pOpcode) return 0;
	tDataType dt = Read<tDataType>();
	return Read<DWORD>();
}

void CDecompiler::Decode()
{
	s_bOpcodeNotMask = false;

	if(s_pOpcode = GetOpcode())
	{
		s_pCommand = s_pOpcode->GetType() == OPCODE_TYPE_COMMAND || s_pOpcode->GetType() == OPCODE_TYPE_UNKNOWN
					? s_pOpcode->Get<CCommand>() : nullptr;
		s_pOperation = s_pOpcode->GetType() == OPCODE_TYPE_OPERATION ? s_pOpcode->Get<COperation>() : nullptr;
		s_pOperator = s_pOperation ? s_pOperation->GetOperator() : nullptr;

		if(s_bOpcodeNotMask)
		{
			CToken * pToken = RegisterToken(TOKEN_CONSTRUCT, CSyntax::GetConstructName(SCR_NOT));
			pToken->SetValue(SCR_NOT);
		}

		if(s_pOpcode->GetType() == OPCODE_TYPE_UNKNOWN)
		{
			if(!s_pCommand->IsOverloaded())
			{
				// Register a one-way overload for the unknown opcode
				sprintf(g_szTempString, "UNKNOWN_%04X", s_pOpcode->GetId());
				CCommand * pCommand = CCommands::Add(s_pOpcode->GetId(), g_szTempString);
				pCommand->SetIsConditional(s_bIsInIF);

				// Unknown version links to declared version but declared version doesnt link back
				s_pCommand->IsOverloaded(true);
				s_pCommand->SetNextOverload(pCommand);

				// Write register_command to the decompiled output
				CToken * pToken = RegisterToken(TOKEN_DIRECTIVE, "#register_command");
				pToken->SetValue(DIRECTIVE_REGISTER_COMMAND);
			}
		}

		if(s_pOpcode->GetType() == OPCODE_TYPE_COMMAND || s_pOpcode->GetType() == OPCODE_TYPE_UNKNOWN)
			DecodeCommand();
		if(s_pOpcode->GetType() == OPCODE_TYPE_OPERATION)
			DecodeOperation();

		if(GetNumRegisteredTokens())
		{
			NextLine();
			DEBUG_TRACE("\n");
		}
	}
}

void CDecompiler::DecodeCommand()
{
	CToken * pToken;
	
	pToken = RegisterToken(TOKEN_COMMAND, s_pCommand->GetName());
	pToken->SetValue(s_pCommand);
	pToken->Trace();
	
	for(int i=0; i < s_pCommand->GetNumArgs(); i++)
	{
		CParamType pt(*s_pCommand->GetArg(i)->GetType());

		WORD et = pt.GetExtendedType();

		if(et & ET_IMMEDIATE_DATA)
		{
			int nVal;
			float fVal;
			pToken = nullptr;
			switch(pt.GetBasicType())
			{
			case PT_CONST:
			case PT_INT:
				if(pt.GetSize() == 1) nVal = Read<BYTE>();
				else if(pt.GetSize() == 2) nVal = Read<WORD>();
				else nVal = Read<DWORD>();
				pToken = RegisterToken(TOKEN_VALUE);
				pToken->SetValue(nVal);
				pToken->SetValueType(PT_INT);
				break;
			case PT_FLOAT:
				fVal = Read<float>();
				pToken = RegisterToken(TOKEN_VALUE);
				pToken->SetValue(fVal);
				pToken->SetValueType(PT_FLOAT);
				break;
			case PT_TEXT_LABEL:
			case PT_STRING:
				ReadString(g_szTempString, pt.GetSize());
				pToken = RegisterToken(TOKEN_STRING, g_szTempString);
				pToken->SetValue(pt.GetSize());
				pToken->SetValueType(PT_STRING);
				break;
			}

			if(pToken && s_pOpcode->GetType() != OPCODE_TYPE_UNKNOWN) pToken->Trace();
		}
		else
		{
			tDataType dt;

			if(et & ET_VAR_EXTRA_PARAMS)
			{
				while((dt = Read<tDataType>()) != DT_END)
				{
					DecodeParam(pt, dt, i);
				}
			}
			else if(et & ET_NUM_EXTRA_PARAMS)
			{
				int num;
				switch(dt = Read<tDataType>())
				{
				case DT_END:
					break;
				case DT_BYTE:
					num = Read<char>();
					break;
				case DT_WORD:
					num = Read<short>();
					break;
				case DT_DWORD:
				default:
					num = Read<int>();
					break;
				case DT_FLOAT:
					num = (int)Read<float>();
					break;
				}

				do
				{
					if(num > 0) num--;
					dt = Read<tDataType>();
					DecodeParam(pt, dt, i);
				}
				while(num && dt != DT_END);
			}
			else
			{
				dt = Read<tDataType>();
				DecodeParam(pt, dt, i);
			}
		}
	}

	if(s_pOpcode->GetType() == OPCODE_TYPE_UNKNOWN)
	{
		NextLine();
		DEBUG_TRACE("\n");

		for(int idx=0; idx < GetNumRegisteredTokens(); idx++)
		{
			pToken = GetToken(s_nNumLinesParsed - 1, idx);
			
			if(!pToken) break;

			CToken * pTokenB = RegisterToken(pToken->GetType(), pToken->GetString());
			pTokenB->SetValueType(pToken->GetValueType());
			pTokenB->SetValue(pToken->GetIntValue());
		}
	}
}

void CDecompiler::DecodeOperation()
{
	CToken * pToken = nullptr;

	DecodeOperand(false);

	char op[4];
	strncpy(op, s_pOperator->GetOperatorString(), 3);
	op[3] = 0;
	pToken = RegisterToken(TOKEN_OPERATOR, op);
	pToken->SetValue(s_pOperation);
	pToken->Trace();

	DecodeOperand(true);
}

void CDecompiler::DecodeOperand(bool bRight)
{
	CToken * pToken;
	auto pType = bRight ? s_pOperation->GetRightType() : s_pOperation->GetLeftType();
	WORD et = pType->GetExtendedType();

	bool	bVarIsArray = false,
			bVarIsLocal = false,
			bCaseLanded = false;

	int strsize = 0,
		nValue = 0;

	if(et & ET_IMMEDIATE_DATA)
	{
		int nVal;
		float fVal;
		pToken = nullptr;
		switch(pType->GetBasicType())
		{
		case PT_CONST:
		case PT_INT:
			if(pType->GetSize() == 1) nVal = Read<BYTE>();
			else if(pType->GetSize() == 2) nVal = Read<WORD>();
			else nVal = Read<DWORD>();
			pToken = RegisterToken(TOKEN_VALUE);
			pToken->SetValue(nVal);
			pToken->SetValueType(PT_INT);
			break;
		case PT_FLOAT:
			fVal = Read<float>();
			pToken = RegisterToken(TOKEN_VALUE);
			pToken->SetValue(fVal);
			pToken->SetValueType(PT_FLOAT);
			break;
		case PT_TEXT_LABEL:
		case PT_STRING:
			ReadString(g_szTempString, pType->GetSize());
			pToken = RegisterToken(TOKEN_STRING, g_szTempString);
			pToken->SetValue(pType->GetSize());
			pToken->SetValueType(PT_STRING);
			break;
		}

		if(pToken) pToken->Trace();
	}
	else
	{
		tDataType dt;

		if(et & ET_VAR_EXTRA_PARAMS)
		{
			while((dt = Read<tDataType>()) != DT_END)
			{
				DecodeParam(*pType, dt, -1);
			}
		}
		else if(et & ET_NUM_EXTRA_PARAMS)
		{
			int num;
			switch(dt = Read<tDataType>())
			{
			case DT_END:
				break;
			case DT_BYTE:
				num = Read<char>();
				break;
			case DT_WORD:
				num = Read<short>();
				break;
			case DT_DWORD:
			default:
				num = Read<int>();
				break;
			case DT_FLOAT:
				num = (int)Read<float>();
				break;
			}

			do
			{
				if(num > 0) num--;
				dt = Read<tDataType>();
				DecodeParam(*pType, dt, -1);
			}
			while(num && dt != DT_END);
		}
		else
		{
			dt = Read<tDataType>();
			DecodeParam(*pType, dt, -1);
		}
	}
}

void CDecompiler::DecodeParam(CParamType &pt, tDataType dt, int nParam = -1)
{
	CToken * pToken;
	CEnum * pEnum;
	WORD et = pt.GetExtendedType();

	bool	bVarIsArray = false,
			bVarIsLocal = false,
			bCaseLanded = false;

	int strsize = 0;

	switch(dt)
	{
	case DT_BYTE:
	case DT_WORD:
	case DT_DWORD:
	case DT_FLOAT:
		pEnum = nullptr;

		if(s_pOpcode->GetType() == OPCODE_TYPE_COMMAND)
			pEnum = s_pCommand->GetArg(nParam)->GetEnum();
		else if(s_pOpcode->GetType() == OPCODE_TYPE_OPERATION)
			pEnum = nParam ? s_pOperation->GetRightEnum() : s_pOperation->GetLeftEnum();

		if(pEnum)
		{
			pToken = RegisterToken(TOKEN_VALUE,
				pEnum->GetEnumeratorWithValue(dt == DT_FLOAT ? GetValue<int>(dt) : GetValue<float>(dt))->GetName()
			);
			pToken->SetValueType(PT_CONST);
			pToken->Trace();
			break;
		}

		pToken = RegisterToken(TOKEN_VALUE);
			
		if(dt == DT_FLOAT) 
		{
			pToken->SetValue(GetValue<float>(dt));
			pToken->SetValueType(PT_FLOAT);
		}
		else
		{
			int nVal = GetValue<int>(dt);
			pToken->SetValue(nVal);
			pToken->SetValueType(PT_INT);
				
			// Convert model ID to model name
			if(et == ET_MODEL)
			{
				CConstant *pConstant = nVal < 0 ? s_Models[-nVal] : CConstants::GetModelByID(nVal);
				if(pConstant)
					pToken->SetString(pConstant->GetName());
			}

			// Convert 1/0 to TRUE/FALSE
			else if(et == ET_BOOLEAN)
				pToken->SetString(nVal	? CSyntax::GetConstructName(SCR_TRUE)
										: CSyntax::GetConstructName(SCR_FALSE));
		}

		pToken->Trace();
		break;

	case DT_LVAR_ARRAY:
	case DT_LVAR_TEXTLABEL_ARRAY:
	case DT_LVAR_STRING_ARRAY:
		if(!bCaseLanded)
		{
			bCaseLanded = true;
			bVarIsLocal = true;
			bVarIsArray = true;
		}
			
	case DT_VAR_ARRAY:
	case DT_VAR_TEXTLABEL_ARRAY:
	case DT_VAR_STRING_ARRAY:
		if(!bCaseLanded)
		{
			bCaseLanded = true;
			bVarIsArray = true;
		}
			
	case DT_LVAR:
	case DT_LVAR_TEXTLABEL:
	case DT_LVAR_STRING:
		if(!bCaseLanded)
		{
			bCaseLanded = true;
			bVarIsLocal = true;
		}

	case DT_VAR:
	case DT_VAR_TEXTLABEL:
	case DT_VAR_STRING:
		DecodeVarParam(bVarIsLocal, bVarIsArray);
		break;

	case DT_TEXTLABEL:
		strsize = 8;
	case DT_STRING:
		if(!strsize) strsize = 16;
	case DT_VARLEN_STRING:
		if(!strsize) strsize = Read<BYTE>();

		ReadString(g_szTempString, strsize);
		g_szTempString[strsize] = '\0';
		pToken = RegisterToken(TOKEN_STRING, g_szTempString);
		pToken->SetValue(strsize);
		pToken->SetValueType(dt == DT_VARLEN_STRING ? PT_TEXT_LABEL : PT_STRING);
		pToken->Trace();
		break;
	}
}

void CDecompiler::DecodeVarParam(bool bIsLocal, bool bIsArray)
{
	CVariable	* pVariable = nullptr, * pArrayIndex = nullptr;
	CToken		* pToken;
	CParamType	pt;
	tVarIndex	varidx = Read<tVarIndex>(),
				arridx;
	tArraySize	arrsize;
	tArrayFlag	arrflag;

	bool bInvalidVarIdx = varidx < 0 || varidx > (s_nVarSpaceSize / 4);

	if(bIsArray)
	{
		arridx = Read<tVarIndex>();
		arrsize = Read<tArraySize>();
		arrflag = Read<tArrayFlag>();

		arridx /= 4;

		if(varidx > 0 && varidx < (s_nVarSpaceSize / 4) && !(pArrayIndex = s_Variables[arridx]))
		{
			sprintf(g_szTempString, arrflag & ARRAY_LVAR ? "LVAR_%d" : "VAR_%d", arridx);

			pArrayIndex = CVariables::Add(
				g_szTempString,
				CParamType(pt.GetBasicType() & PT_ANYBASIC | (arrflag & ARRAY_LVAR ? PT_LVAR : PT_VAR)),
				arridx
			);

			s_Variables[arridx] = pArrayIndex;
		}
	}
	
	if(!bIsLocal) varidx /= 4;

	if(varidx > 0 && varidx < (s_nVarSpaceSize / 4) && !(pVariable = s_Variables[varidx]))
	{
		sprintf(g_szTempString, bIsLocal ? "LVAR_%d" : "VAR_%d", varidx);
				
		pVariable = CVariables::Add(
			g_szTempString,
			CParamType((pt.GetBasicType() & PT_ANYBASIC) | (bIsLocal ? PT_LVAR : PT_VAR)),
			varidx, bIsArray ? arrsize : 0
		);

		s_Variables[varidx] = pVariable;
	}

	if(pVariable)
	{
		pToken = RegisterToken(TOKEN_VAR, pVariable->GetName());
		pToken->SetValue(pVariable);
		pToken->Trace();
	}

	if(bIsArray && pArrayIndex)
	{
		pToken = RegisterToken(TOKEN_VARINDEX, pArrayIndex->GetName());
		pToken->SetValue(pArrayIndex);
		pToken->Trace();
	}
}

bool CDecompiler::LoadBuffer()
{
	if(IsEndOfFile(s_hInFile)) return false;
	if(!IsEndOfBuffer())
	{
		SeekFile(s_hInFile, s_nNumBytesRead, SEEK_SET);
	}
	s_nInputBufferSize = ReadBytesFromFile(s_hInFile, s_pInputBuffer, s_nInputBufferSize);
	s_pBuffer = s_pInputBuffer;
	return true;
}

void CDecompiler::SkipTo(DWORD offset)
{
	if(offset <= s_nNumBytesRead) return;

	int left = s_pInputBuffer + s_nInputBufferSize - s_pBuffer;

	if(offset < s_nNumBytesRead + left)
	{
		s_pBuffer += offset - s_nNumBytesRead;
		s_nNumBytesRead = offset;
	}
	else
	{
		SeekFile(s_hInFile, offset, SEEK_SET);
		s_nNumBytesRead = offset;
		LoadBuffer();
	}
}

template<typename T>
T CDecompiler::ReadBuffer()
{
	T v;
	int left = s_pInputBuffer + s_nInputBufferSize - s_pBuffer;

	if(sizeof(T) > left) LoadBuffer();

	v = *(T*)s_pBuffer;
	s_pBuffer += sizeof(T);
	s_nNumBytesRead += sizeof(T);
	return v;
}

#endif